#include "checkers/FunctionCallChecker.h"

extern ReportSpace::Report external_bug_report;

void FunctionCallChecker::check() {
    readConfig();
    checkAllFunctions();
}

void FunctionCallChecker::readConfig() {
    std::unordered_map<std::string, std::string> ptrConfig =
            configure->getOptionBlock("FunctionCallChecker");
    param_size = stoi(ptrConfig.find("param_size")->second);
    array_size = stoi(ptrConfig.find("array_size")->second);
}

const std::string getString_of_Expr(const Expr *expr) {
    LangOptions L0;
    L0.CPlusPlus = 1;
    std::string buffer1;
    llvm::raw_string_ostream strout1(buffer1);
    expr->printPretty(strout1, nullptr, PrintingPolicy(L0));
    return strout1.str();
}

uint64_t computeBufferSize(VarDecl *vd) {
    uint64_t bufferSize = std::numeric_limits<uint64_t>::max();

    auto arrayType = vd->getType();

    // int arr[100];
    if (const ConstantArrayType *cat = dyn_cast<ConstantArrayType>(arrayType)) {
        bufferSize = cat->getSize().getLimitedValue();
    }

    // int size = 100; int arr[size];
    if (const VariableArrayType *vat = dyn_cast<VariableArrayType>(arrayType)) {
        auto sizeExpr = vat->getSizeExpr()->IgnoreParenCasts()->IgnoreImpCasts();
        if (DeclRefExpr *sizeDre = dyn_cast<DeclRefExpr>(sizeExpr)) {
            if (VarDecl *sizeVd = dyn_cast<VarDecl>(sizeDre->getDecl())) {
                if (sizeVd->hasInit()) {
                    auto sizeInit =
                            sizeVd->getInit()->IgnoreParenCasts()->IgnoreImpCasts();
                    if (IntegerLiteral *integerSize =
                            dyn_cast<IntegerLiteral>(sizeInit)) {
                        bufferSize = integerSize->getValue().getLimitedValue();
                    }
                }
            }
        }
    }
    return bufferSize;
}

vector<string> mysplit(const string &str, const string &pattern) {
    vector<string> res;
    if (str == "")
        return res;
    string strs = str + pattern;
    size_t pos = strs.find(pattern);
    while (pos != strs.npos) {
        string temp = strs.substr(0, pos);
        res.push_back(temp);
        strs = strs.substr(pos + 1, strs.size());
        pos = strs.find(pattern);
    }
    return res;
}


void FunctionCallChecker::addReport(std::string loc, std::string varName, std::string dec) {
    vector<string> res = mysplit(loc, ":");
    int lineNum = atoi(res[1].c_str());
    int colNum = atoi(res[2].c_str());
    ReportSpace::Defect *defect = new ReportSpace::Defect(ReportSpace::DefectName::ExpensiveFunctionCall,
                                                          ReportSpace::DefectType::Warning,
                                                          res[0], lineNum, colNum, varName);
    defect->addDesc(dec);
    external_bug_report.addToReport(defect);
}

void FunctionCallChecker::checkAllFunctions() {
    std::vector<ASTFunction *> funcs = manager->getFunctions(true);
    int funcNums = funcs.size();
    int count = 0;
    for (ASTFunction *F : funcs) {
        count++;
        process_bar(float(count)/funcNums);
        FunctionDecl *funDecl = manager->getFunctionDecl(F);
        if (funDecl->hasBody() == false || funDecl->isInStdNamespace() == true ||
            funDecl->isFunctionOrMethod() == false) {
            continue;
        }
        //cout << "Begin analysis in function " + F->getName() << endl;

        const clang::ASTContext &astcontext = funDecl->getASTContext();
        std::string funcName = funDecl->getNameAsString();
        if (funDecl->param_size() > param_size) {
            //has too much parameters
            std::string loc =
                    funDecl->getLocation().printToString(astcontext.getSourceManager());
            addReport(loc, funcName, "[Too many function parameters] Function " + funcName + "() has more than " +
                                     to_string(param_size) + " parameters");
        }

        string returnValue = "";
        int numOfReturnStmt = 0;
        bool oneReturnValue = true;
        bool returnTypeIsConstant = false;
        std::string loc = "";
        const VarDecl* returnVar = nullptr;

        map<const clang::VarDecl *, int> varMap;

        std::unique_ptr<CFG> &cfg = manager->getCFG(F);
        for (CFG::iterator iter = cfg->begin(); iter != cfg->end(); ++iter) {
            CFGBlock *block = *iter;
            for (CFGBlock::iterator iterblock = block->begin(); iterblock != block->end(); ++iterblock) {
                CFGElement element = (*iterblock);
                if (element.getKind() != CFGElement::Kind::Statement)
                    continue;
                const Stmt *it = ((CFGStmt *) &element)->getStmt();
                if (it == nullptr)
                    continue;

                Expr *expr = (Expr *) it;
                //cout << "now process expr " + getString_of_Expr(expr) << endl;
                if (expr->getStmtClass()) {
                    switch (expr->getStmtClass()) {
                        case clang::Stmt::DeclStmtClass: {
                            if (const DeclStmt *declStmt = dyn_cast<DeclStmt>(it)) {
                                if (const VarDecl *varDecl = dyn_cast<VarDecl>(declStmt->getSingleDecl())) {
                                    string variableName = varDecl->getQualifiedNameAsString();
                                    auto type = varDecl->getType();
                                    if (type->isBuiltinType()) {
                                        if (varDecl->hasInit()) {
                                            varMap[varDecl] = varMap[varDecl] + 1;
                                        } else {
                                            varMap[varDecl] = 0;
                                        }
                                    } else if (type->isArrayType()) {
                                        auto elementType = type->getBaseElementTypeUnsafe();
                                        if (varDecl->hasInit()) {
                                            //check array size
                                            VarDecl *vd = (VarDecl *) varDecl;
                                            int array_size = computeBufferSize(vd);
                                            if (array_size > this->array_size) {
                                                std::string loc =
                                                        varDecl->getLocation().printToString(
                                                                astcontext.getSourceManager());
                                                addReport(loc, variableName,
                                                          "[Time-consuming initialization] The size of array \"" +
                                                          variableName +
                                                          "\" exceeds " +
                                                          to_string(this->array_size) +
                                                          ", so the assignment takes too much time");
                                            }

                                            if (elementType->isBuiltinType()) {
                                                varMap[varDecl] = varMap[varDecl] + 1;
                                            } else {
                                                varMap[varDecl] = varMap[varDecl] + 2;
                                            }
                                        } else {
                                            if (!elementType->isBuiltinType()) {
                                                varMap[varDecl] = varMap[varDecl] + 2;
                                            } else {
                                                varMap[varDecl] = 0;
                                            }
                                        }
                                    } else {
                                        varMap[varDecl] = varMap[varDecl] + 2;
                                    }
                                }
                            }
                            break;
                        }

                        case clang::Stmt::BinaryOperatorClass: {
                            const VarDecl *varDecl = nullptr;
                            if (BinaryOperator *binaryOperator = dyn_cast<BinaryOperator>(expr)) {
                                if (binaryOperator->isAssignmentOp()) {
                                    Expr *left = binaryOperator->getLHS();
                                    if (ArraySubscriptExpr *arrayExpr = dyn_cast<ArraySubscriptExpr>(left)) {
                                        //array variable
                                        Expr *baseExpr = arrayExpr->getBase()->IgnoreParenCasts()->IgnoreImpCasts();
                                        if (DeclRefExpr *dre = dyn_cast<DeclRefExpr>(baseExpr)) {
                                            if (VarDecl *vd = dyn_cast<VarDecl>(dre->getDecl())) {
                                                auto arrayType = vd->getType();
                                                auto elementType = arrayType->getBaseElementTypeUnsafe();
                                                if (elementType->isBuiltinType()) {
                                                    varMap[vd] = varMap[vd] + 1;
                                                }
                                            }
                                        }
                                    } else {
                                        //other type variable
                                        if (const DeclRefExpr *declRefExpr = dyn_cast<DeclRefExpr>(left)) {
                                            if (varDecl = dyn_cast_or_null<VarDecl>(declRefExpr->getDecl())) {
                                                varMap[varDecl] = varMap[varDecl] + 1;
                                            }
                                        }
                                    }
                                }
                            }
                            break;
                        }

                        case clang::Stmt::UnaryOperatorClass: {
                            if (UnaryOperator *unaryOperator = dyn_cast<UnaryOperator>(expr)) {
                                if (ArraySubscriptExpr *arrayExpr = dyn_cast<ArraySubscriptExpr>(
                                        unaryOperator->getSubExpr())) {
                                    Expr *baseExpr = arrayExpr->getBase()->IgnoreParenCasts()->IgnoreImpCasts();
                                    if (DeclRefExpr *dre = dyn_cast<DeclRefExpr>(baseExpr)) {
                                        if (VarDecl *vd = dyn_cast<VarDecl>(dre->getDecl())) {
                                            auto arrayType = vd->getType();
                                            auto elementType = arrayType->getBaseElementTypeUnsafe();
                                            if (elementType->isBuiltinType()) {
                                                varMap[vd] = varMap[vd] + 1;
                                            }
                                        }
                                    }
                                } else {
                                    if (const DeclRefExpr *declRefExpr = dyn_cast_or_null<DeclRefExpr>(
                                            unaryOperator->getSubExpr())) {
                                        if (const VarDecl *varDecl = dyn_cast_or_null<VarDecl>(
                                                declRefExpr->getDecl())) {
                                            varMap[varDecl] = varMap[varDecl] + 1;
                                        }
                                    }
                                }
                            }
                            break;
                        }

                        case clang::Stmt::ReturnStmtClass: {
                            if (oneReturnValue) {
                                if (const ReturnStmt *rstmt = dyn_cast<ReturnStmt>(it)) {
                                    const Expr *expr = rstmt->getRetValue()->IgnoreParenCasts()->IgnoreImpCasts();
                                    //cout << "return value " + getString_of_Expr(expr) << endl;
                                    if (expr != nullptr) {
                                        //if (expr->getStmtClass())
                                            //cout << expr->getStmtClassName() << endl;
                                        Expr::EvalResult evalres;
                                        if (expr->EvaluateAsConstantExpr(evalres,
                                                                        /*Expr::ConstExprUsage::EvaluateForMangling,*/ 
                                                                         astcontext)) {
                                            //cout << "is constant expr " << endl;
                                            returnTypeIsConstant = true;
                                        }else if (const DeclRefExpr *dre = dyn_cast<DeclRefExpr>(expr)) {
                                            //cout << "dre " << endl;
                                            if (const VarDecl *vd = dyn_cast<VarDecl>(dre->getDecl())) {
                                                //cout << "variable " + vd->getNameAsString() << endl;
                                                const Expr *e = vd->getInit()->IgnoreParenBaseCasts()->IgnoreImpCasts();
                                                Expr::EvalResult evalres;
                                                if (e != nullptr) {
                                                    //cout << "the init " + getString_of_Expr(e) << endl;
                                                    if (e->EvaluateAsConstantExpr(evalres,
                                                                        /*Expr::ConstExprUsage::EvaluateForMangling,*/ 
                                                                                  astcontext)) {
                                                        returnTypeIsConstant = true;
                                                    }
                                                }
                                                //cout << "vd->isconstantexpr()"+ vd->isConstexpr() << endl;
                                                //returnVar = vd;
                                            }
                                        }
                                        numOfReturnStmt++;
                                        if (numOfReturnStmt != 1 && returnValue != getString_of_Expr(expr)) {
                                            oneReturnValue = false;
                                            break;
                                        } else if (numOfReturnStmt == 1) {
                                            returnValue = getString_of_Expr(expr);
                                            loc = expr->getExprLoc().printToString(astcontext.getSourceManager());
                                        }
                                    }
                                }
                            }
                            break;
                        }
                    }
                }

            }
        }

        if (oneReturnValue && numOfReturnStmt != 0 && returnTypeIsConstant) {
            if (returnTypeIsConstant){
                addReport(loc, funcName, "[Meaningless return value] Function " + funcName +
                                         "() has only one unchanged return value, it's better to cancel the return value");
            }else {
                //returnVar
            }

        }

        map<const clang::VarDecl *, int>::iterator it = varMap.begin();
        while (it != varMap.end()) {
            if (it->second == 1) {
                const VarDecl *varDecl = it->first;
                if (varDecl->hasLocalStorage()) {
                    const Expr *expr = varDecl->getInit()->IgnoreParenBaseCasts()->IgnoreImpCasts();
                    Expr::EvalResult evalres;
                    if (expr != nullptr) {
                        //cout << "the init " + getString_of_Expr(expr) << endl;
                        if (expr->EvaluateAsConstantExpr(evalres, /*Expr::ConstExprUsage::EvaluateForMangling,*/ 
                                                         astcontext)) {
                            std::string loc =
                                    varDecl->getLocation().printToString(astcontext.getSourceManager());
                            addReport(loc, varDecl->getQualifiedNameAsString(), "[Unchanged variable] The variable \"" +
                                                                                varDecl->getQualifiedNameAsString() +
                                                                                "\" has not changed since it was assigned once");
                        }
                    }
                }
            }
            it++;
        }
    }
    return;
}
/*
bool FunctionCallChecker::isUnchange(Expr* expr,ASTContext& astContext){
    expr = expr->IgnoreParenCasts()->IgnoreImpCasts();
    Expr::EvalResult evalResult;
    if (expr->EvaluateAsConstantExpr(evalResult,Expr::ConstExprUsage::EvaluateForMangling,astContext)){
        return true;
    }else{
        if (expr->getStmtClass()){
            switch (expr->getStmtClass()) {
                case clang::Stmt::DeclRefExprClass:{
                    break;
                }
                case clang::Stmt::BinaryOperatorClass:{
                    break;
                }
            }
        }
    }
}*/


